1. issue
More and more lending pools are offering flash loans. In this case, a new pool has launched that is offering flash loans of DVT tokens for free.
The pool holds 1 million DVT tokens. You have nothing.
To pass this challenge, take all tokens out of the pool. If possible, in a single transaction.
目标:身无分文的你要将借贷池的钱全取出来。
2. analysing
2.1 寻找balance是什么
通过使用ethers.provider.getBalance()
查询 pool.address
的balance可以看到为 0, 但是通过token.balanceOf()
可以查看到 balance为 1000000 ether。所以,题目所要借光的是 ERC20 token,要想对token的值动手,只能调用 ERC20 下的 转账操作,比如transfer(), transferFrom()
。
2.2 TrusterLenderPool.sol
分析flashLoan
函数
1 | function flashLoan(uint256 amount, address borrower, address target, bytes calldata data) |
flashLoan写的很简单,需要注意的是,
token.transfer(borrower, amount)
看到是token给borrower转账,这里要引起注意;target.functionCall(data);
这行代码需要层层追踪才可以发现里面的新天地。解读
functionCall
:对
data
进行追踪,发现是functionCallWithValue
在被使用的
1
2
3
4
5
6
7
8
9
10
11
12 function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResult(success, returndata, errorMessage);
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
中,有data
,这行代码其实是在调用函数,而函数就是被编译成为字节码的data
,所以target
就理所应当是包含data
函数的合约地址。再进一步分析,我们的初衷是改变 balance,能改变balance的只有 转账操作,一种是 使用
transfer
另一种中是transferFrom
如果使用transfer
1 | function transfer(address to, uint256 amount) public virtual returns (bool) { |
要符合题意既要把pool的钱花光,还要给play.address地址转入一百万ether,使用 transfer
能满足后者,但不能满足前一个条件
1 | let attackToken1 = token.connect(deployer); |
所以只能使用 transferFrom
如果使用 transferFrom
1 | function transferFrom( |
只要我们能够将transferFrom函数成功调用,就可以解决问题了。from的值为pool.address, to的值为player.address。能执行操作 1000000ether 就 必须让 pool.address 给 play.address approve 1000000ether的操作权限。
所以,data的值就是 transferFrom 的字节码。
approve
函数
1 | function approve(address spender, uint256 amount) public virtual returns (bool) { |
由call的使用原理可知,msg.sender
就是 pool的合约地址。
3. solving
3.1 TrusterHack.sol
1 | // SPDX-License-Identifier: MIT |
注意:在合约中是不能调用transferFrom函数的,因为你不能将任意一个地址强转成ERC20类型,然后调用其中的方法(个人理解)
3.2 challenge.js
1 | it('Execution', async function () { |
解题成功~
还有另一种解题方式,通过etherjs 来解题的,原理都一样,实现的方法不同: 方法二